Supplementary Material (B) - Testing the Circumplex Structure of the Soundscape Survey
Accompanying the paper: “Soundscape descriptors in eighteen languages: translation and validation through listening experiments
Authors
Affiliation
Andrew Mitchell
University College London
Francesco Aletta
University College London
Published
November 21, 2023
1 Setup
Code
# Import the required packagesimport pandas as pdimport seaborn as snsfrom pathlib import Pathimport matplotlib.pyplot as pltfrom scipy.optimize import curve_fitimport numpy as npfrom datetime import datetimeimport circumpleximport jsontoday = datetime.today().strftime('%Y-%m-%d')
Code
# Define the scales and angles to be usedscales = ["PAQ1", "PAQ2", "PAQ3", "PAQ4", "PAQ5", "PAQ6", "PAQ7", "PAQ8"]eq_angles = [0, 45, 90, 135, 180, 225, 270, 315]# Define the data and output foldersdata_folder = Path("../data/")output_folder = Path(f"../outputs/{today}")# Load datasatp = pd.read_excel(data_folder /"SATP Dataset v1.4.xlsx")lvls = pd.read_excel(data_folder /"LLAN.xlsx")# Clean up the lvls datalvls = lvls.groupby("Mark/Group Name").max().drop("Channel Name", axis=1)lvls.rename(columns={"L/dB(SPL)": "max_Leq", "L(A)/dB(SPL)": "max_LAeq", "N/soneGF": "max_N", "L90(A)/dB(SPL)": "max_LA90"}, inplace=True)# Add the levels to the satp datasatp = satp.merge(lvls, left_on="Recording", right_on ="Mark/Group Name", right_index=True)# Load the results from the latest SEM analysissem_res = pd.read_csv(output_folder /"sem-fit-ipsatized.csv")sem_res.drop("Unnamed: 0", axis=1, inplace=True)# In some cases, the SEM flips the angles (i.e. vibrant is at 315 degrees instead of 45).# This function checks for this and corrects it, to ensure all the scales are in the # correct order, but without changing the relationship between the angles, as identified by the SEM.)# First, get the angles from the SEM resultsang_df = sem_res[sem_res['Model Type'] =='Equal comm.'][["Language"] + scales]ang_df.set_index("Language", inplace=True)def check_inverse_angles(language_angles):"""Check if the angles are inverse"""if language_angles[1] > language_angles[2] or language_angles[2] > language_angles[3]:returnTrueelse:returnFalse# Then, check if the angles are inverse, and if so, correct themfor lang in ang_df.index:if check_inverse_angles(ang_df.loc[lang].values): ang_df.loc[lang][1:] =360- ang_df.loc[lang][1:]ang_dict = ang_df.T.to_dict(orient="list")ang_dict
2 Calculate the SEM fit score
Code
# Define the thresholds for the SEM fit criteriathresholds = {"p": 0.05,"CFI": 0.92, # ours# "CFI": 0.9, # Rogoza"GFI": 0.9,"AGFI": 0.85,"SRMR": 0.08,"MCSC": -0.7,# "RMSEA": 0.08, # ours"RMSEA": 0.13, # Rogoza"GDIFF": 25,}# Choose which criteria to include in the final score# incl_in_score = ['p', 'CFI', 'GFI', 'SRMR', 'MCSC'] # ours# incl_in_score = ['p', 'CFI', 'GFI', 'AGFI', 'RMSEA'] # Rogozaincl_in_score = ['p', 'CFI', 'GFI', 'AGFI', 'SRMR'] # mixed# Define the thresholds for the final scorepass_thresh =5tent_thresh =4# Calculate the final scoresem_res['p_pass'] = sem_res['p'] <= thresholds['p']sem_res['CFI_pass'] = sem_res['CFI'] >= thresholds['CFI']sem_res['GFI_pass'] = sem_res['GFI'] >= thresholds['GFI']sem_res['AGFI_pass'] = sem_res['AGFI'] >= thresholds['AGFI']sem_res['SRMR_pass'] = sem_res['SRMR'] <= thresholds['SRMR']# sem_res['MCSC_pass'] = sem_res['MCSC'] <= thresholds['MCSC']# sem_res['RMSEA_pass'] = sem_res['RMSEA'] <= thresholds['RMSEA']# sem_res['GDIFF_pass'] = sem_res['GDIFF'] <= thresholds['GDIFF']sem_res['Score'] = sem_res[[x +'_pass'for x in incl_in_score]].sum(axis=1)sem_res['Score'] = sem_res['Score'].astype(int)sem_res['passing'] = pd.cut(sem_res['Score'], bins=[0, tent_thresh, pass_thresh, 7], labels=['Fail', 'Tentative', 'Pass'], right=False)# Save the resultssem_res.to_excel(output_folder /f"{today}_sem-fit-results-Rogoza.xlsx", index=False)sem_res[["Language", "Model Type", "Score", "passing"]].loc[sem_res["Model Type"] =="Equal comm."].sort_values("Score", ascending=False)
def transpose_results_lists_to_ssm_results(list_of_results):# Initialize a dictionary to hold the lists results_dict = {group: [] for group in list_of_results[0].groups}for ssm_res in list_of_results:for ssm_param in ssm_res.results:# Check if the group exists in the dictionaryif ssm_param.group in results_dict:# Append the ssm_param to the appropriate list in the dictionary results_dict[ssm_param.group].append(ssm_param)return results_dictper_lang_eq_locating_lists = transpose_results_lists_to_ssm_results(eq_locating_lists)per_lang_corr_locating_lists = transpose_results_lists_to_ssm_results(corr_locating_lists)for key, val in per_lang_eq_locating_lists.items(): per_lang_eq_locating_lists[key] = circumplex.SSMResults(val)for key, val in per_lang_corr_locating_lists.items(): per_lang_corr_locating_lists[key] = circumplex.SSMResults(val)per_lang_eq_locating_lists['eng'].plot()per_lang_corr_locating_lists['eng'].plot()
(<Figure size 672x480 with 1 Axes>, <PolarAxes: >)
@online{mitchell2023,
author = {Mitchell, Andrew and Aletta, Francesco},
title = {Supplementary {Material} {(B)} - {Testing} the {Circumplex}
{Structure} of the {Soundscape} {Survey}},
date = {2023-11-21},
langid = {en}
}
For attribution, please cite this work as:
Mitchell, Andrew, and Francesco Aletta. 2023. “Supplementary
Material (B) - Testing the Circumplex Structure of the Soundscape
Survey.” November 21, 2023.